<?php
/* 
* @Title:  [Mysql模型类,包括查询结果集和执行动作两个方法.基于MYSQLI扩展]
* @Author: {chenlei} [chenleib5@126.com]
* @Date:   2014-03-06 17:01:39
* @Last Modified by:   Administrator
* @Last Modified time: 2014-03-13 11:03:37
* @Copyright:  [hn7m.com]
*/
/**
 * 本模型类中的属性,方法顺序如下:
 * 1.定义模型类中的属性
 * 2.定义检测mysqli库是否存在的方法
 * 3.定义初始化SQL语句方法
 * 4.定义MYSQLI从连接到执行到关闭数据库连接的所用方法
 * 5.定义最终用户使用的增(add)/删(del)/改(update)/查(find/findall)四个方法
 */
class Model{

/*********************1.定义模型类中的属性*******************/
	//定义的表名
	private $table;
	//初始化信息
	private $opt;
	//保存连接信息
	private static $link = NULL;
	//保存分组信息
	private $group;

	public function __construct($table){
		//给数据库表加前缀
		$this->table =C('DB_PREFIX') . $table;
		//连接数据库
		$this->_connect();
		$this->_opt();
	}
	/**
	 * [_check_mysqli 检测mysqli库是否存在]
	 * @return [type] [description]
	 */
	
/*********************2.定义检测mysqli库是否存在的方法*******************/
	private function _check_mysqli(){
		return extension_loaded('mysqli');
	}

/*********************3.定义初始化SQL语句方法*******************/
	/**
	 * [_opt 初始化SQL语句方法]
	 * @return [type] [description]
	 */
	private function _opt(){
		$this->opt = array(
			'field' 	=>	'*',
			'where' 	=>  '',
			'group'		=> 	'',
			'having'	=>	'',
			'order'		=>	'',
			'limit'		=>	''
			);
	}

	//1.执行field方法就是按field相同的字段名查找数据
	public function field($field){
		$this->opt['field'] =$field;
		return $this;
	}
	//2.执行Where方法就是指定查询条件
	public function where($condition){
		$this->opt['where'] = " WHERE " . $condition;
		return $this;
	}

	public function count($field){
		$this->opt['field'] =$field;
		return $this;
	}
	//3.执行group方法就是指定分组依据
	public function group($condition){
		$this->opt['group'] = " GROUP BY " . $condition;
		$this->group = $condition;
		return $this;
	}
	//4.执行having方法就是按分组指定条件
	public function having($condition){
		$group = $this->group;
		$this->opt['having'] = " HAVING " . $group . '='.'"'.$condition.'"';
		return $this;
	}
	//5.执行order方法就是指定排序规则
	public function order($order){
		$this->opt['order'] = " ORDER BY " . $order;
		return $this;
	}
	//6.执行limit方法就是限制结果集条数
	public function limit($limit){
		$this->opt['limit'] = " LIMIT " . $limit;
		return $this;
	}

/*********************4.定义MYSQLI从连接到执行到关闭数据库连接的所用方法*******************/
	/**
	 * [_connect 连接数据库方法]
	 * @return [type] [description]
	 */
	private function _connect(){
		if(is_null(self::$link)){
			//使用nysqli连接
			////数据库地址,用户名等信息直接用C函数读取配置项
		
			$link = @new Mysqli(C('DB_HOST'),C('DB_USER'),C('DB_PASSWORD'),C('DB_NAME'));

			//如果数据库连接有错误,返回错误信息.然后直接die掉
			if ($link->connect_errno) {
				halt($link->connect_error);
			}
			//发送编码,编码也从配置项读取
			//$link->query('SET NAMES '.C('DB_CHARSET'));
			//为安全考虑,这里不单单使用SET NAMES uff8,而使用下面的方法,客户端使用二进制,连接和结果端才使用配置项要求的编码
			$link->query("SET CHARACTER_SET_CLIENT=BINARY,CHARACTER_SET_CONNECTION=".C('DB_CHARSET').",CHARACTER_SET_RESULTS=".C('DB_CHARSET'));
			self::$link = $link;
		}	
	}

	
	/**
	 * [query 查询有结果集的方法]
	 * @param  [type] $sql  [SQL语句]
	 * @return [type] $rows [返回所有的关联数组]
	 */
	public function query($sql){
		//如果检测到没有成功加载mysqli库,直接返回falase;
		if(!$this->_check_mysqli()) return false;

		//建立mysql连接
		//$link = $this->link();
		$link = self::$link;
		//发送查询SQL,执行查询
		$result = $link->query($sql);
		//p($sql);
		if(!$result){
			halt($link->error . '<h2 style="color:red">SQL:' . $sql . '</h2>');
		}
		// echo $link->affected_rows;die;
		$rows = array();
		if($link->affected_rows > 0){
			//组合结果集
			while ($row = $result->fetch_assoc()) {
				$rows[] = $row;
			}
		}

		return $rows;

	}
	
	/**
	 * [exe 执行没有结果集的SQL语句,主要是增/删/改]
	 * @param  [type] $sql [SQL语句]
	 * @return [type]      [返回自增的主键ID/获得受影响的记录数]
	 */
	public function exe($sql){
		//如果检测到没有成功加载mysqli库,直接返回falase;
		if(!$this->_check_mysqli()) return false;

		//建立mysql连接
		$link = self::$link;

		//发送查询SQL,执行查询
		$result = $link->query($sql);
		//IF进行限定,增/删/改只能用exe方法,查只能用find/findall方法
		if(is_object($result)){
			halt('查询结果集请用find/findall方法');
		}
		if($result){
			//返回自增的主键ID/获得受影响的记录数
			return $link->insert_id ?  $link->insert_id : $link->affected_rows;
		}else{
			//提示错误,并把SQL语句也打印出来.方便排错
			halt($link->error . '<h2 style="color:red">SQL:' . $sql . '</h2>');
		}

	}

/*************5.定义最终用户使用的增(add)/删(del)/改(update)/查(find/findall)四个方法*******************/
	/**
	 * [add 向数据库增加记录]
	 * @param [type] $data [description]
	 */
	//SQL语句范例:INSERT INTO stu (name,age) VALUES ('陈磊',18);
	public function add($data = NULL){
		//如果不传参,表明是前台页面POST提交的数据
		if(is_null($data)){
			if (IS_POST) {
				//取键名,按,分割成字符串
				$keys = array_keys($_POST);
				$field = implode(',', $keys);
				//安全处理,检测系统是否开启自动转义
				if(!get_magic_quotes_gpc()){
					$safe=addslashes($_POST); 
				}
				$safe = htmlspecialchars($safe);
				//取键值,按,分割成字符串
				$values = array_values($safe);
				$value = '"' .implode('","', $values) .'"';
				//组织SQL语句
				$sql = 'INSERT INTO ' . $this->table . ' ($field) VALUES ($value)';
				//执行SQL语句
				return $this->exe($sql);
			}else{
				halt('请输入要增加的数据');
			}
		//否则就是传参,要求参数必须是数组,操作步骤跟上面一样
		}elseif (is_array($data)) {
			$keys = array_keys($data);
			$field = implode(',', $keys);
			//这里因为是后台管理员进行的增加操作,就不必要做安全处理了
			$values = array_values($data);
			$value = '"' .implode('","', $values) .'"';
			$sql = 'INSERT INTO ' . $this->table . " ($field) VALUES ($value)";
			//p($sql);
			return $this->exe($sql);
		}else{
			//不是数组报错,并提示
			halt('请输入一个数组');
		}
	}

	/**
	 * [del 删除数据库记录]
	 * @param  [type] $data [description]
	 * @return [type]       [description]
	 */
	// SQL语句范例delete from qm_stu where id=2;
	// M('stu')->where('sid=1')->del();
	public function del(){
		if (!empty($this->opt['where'])) {
			//为避免漏写WHERE条件而修改了所有的表记录而产生的悲剧,这里加一个IF判断
			$sql = 'delete from ' . $this->table .$this->opt['where'];
			//p($sql);				
			return $this->exe($sql);				
		}else{
			halt('必须指定where条件');
		}
	}

	/**
	 * [delall 删除数据库表中所有记录]
	 * @return [type] [description]
	 */
	public function delall(){
		$sql =  'truncate ' . $this->table;
		//p($sql);
		return $this->exe($sql);
	}

	/**
	 * [update 修改数据库某条记录]
	 * @param  [type] $data [如果传参,必须是一个数组]
	 * @return [type]       [description]
	 */
	//SQL语句范例:update qm_stu set name='代小飞',age=20 where id =1;
	public function update($data){
		if (!empty($this->opt['where'])) {
			//为避免漏写WHERE条件而修改了所有的表记录而产生的悲剧,这里加一个IF判断
			$sql = 'UPDATE ' . $this->table . ' SET ' .$data. $this->opt['where'];
			//p($sql);				
			return $this->exe($sql);				
		}else{
			halt('必须指定where条件');
		}
	}

	/**
	 * [findall 执行findall方法就是查询所有数据]
	 * [find 执行find方法是查询当前的一条数据]
	 * @return [type] [description]
	 */
	//1.执行findall方法就是查询所有数据
	public function findall(){
		//组织SQL语句
		$sql = "SELECT " . $this->opt['field'] . " FROM " . $this->table . $this->opt['where'] . $this->opt['group'] . $this->opt['having'] . $this->opt['order'] . $this->opt['limit'];
		
		return $this->query($sql);
	}
	//2.执行find方法的原因是:如果只需查一条数据,用findall方法查出来的是一个二维数组,而用find的方法查出来的就是一个一维数组,便于操作;
	public function find(){
		//经过这一步,得到一个二维数组
		$data = $this->limit(1)->findall();
		//取当前键值就是一个一维数组了
		$data = current($data);
		return $data;
	}

}

?>